package bma.common.netty.chunk;

import static org.jboss.netty.channel.Channels.succeededFuture;
import static org.jboss.netty.channel.Channels.write;
import static org.jboss.netty.handler.codec.http.HttpHeaders.is100ContinueExpected;

import java.io.IOException;
import java.util.List;
import java.util.Map.Entry;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBufferFactory;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.TooLongFrameException;
import org.jboss.netty.handler.codec.http.HttpChunk;
import org.jboss.netty.handler.codec.http.HttpChunkTrailer;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpMessage;
import org.jboss.netty.util.CharsetUtil;

public class HttpChunkAggregatorObj {

	private static final ChannelBuffer CONTINUE = ChannelBuffers.copiedBuffer(
			"HTTP/1.1 100 Continue\r\n\r\n", CharsetUtil.US_ASCII);

	private HttpMessage currentMessage;

	public HttpChunkAggregatorObj() {
		super();
	}

	public static boolean handleContinue(ChannelHandlerContext ctx,
			HttpMessage m) {
		if (is100ContinueExpected(m)) {
			write(ctx, succeededFuture(ctx.getChannel()), CONTINUE.duplicate());
			return true;
		}
		return false;
	}

	public void beginMessage(HttpMessage msg, ChannelBufferFactory factory) {
		if (factory != null) {
			msg.setContent(ChannelBuffers.dynamicBuffer(factory));
		}
	}

	public void appendContent(HttpMessage msg, ChannelBuffer buf) {
		ChannelBuffer cb = msg.getContent();
		if (cb == buf)
			return;
		cb.writeBytes(buf);
	}

	public int getContentLength(HttpMessage msg) {
		ChannelBuffer cb = msg.getContent();
		return cb.readableBytes();
	}

	public void endMessage(HttpMessage msg) {

	}

	public HttpMessage messageReceived(Channel ch, Object msg,
			int maxContentLength) throws Exception {

		HttpMessage cmsg = this.currentMessage;

		if (msg instanceof HttpMessage) {
			HttpMessage m = (HttpMessage) msg;

			if (m.isChunked()) {
				// A chunked message - remove 'Transfer-Encoding' header,
				// initialize the cumulative buffer, and wait for incoming
				// chunks.
				List<String> encodings = m
						.getHeaders(HttpHeaders.Names.TRANSFER_ENCODING);
				encodings.remove(HttpHeaders.Values.CHUNKED);
				if (encodings.isEmpty()) {
					m.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
				}
				m.setChunked(false);
				beginMessage(m, ch.getConfig().getBufferFactory());
				this.currentMessage = m;
				return null;
			} else {
				// Not a chunked message - pass through.
				this.currentMessage = null;
				ChannelBuffer cb = m.getContent();
				beginMessage(m, null);
				appendContent(m, cb);
				endMessage(m);
				return m;
			}
		} else if (msg instanceof HttpChunk) {
			// Sanity check
			if (cmsg == null) {
				throw new IllegalStateException("received "
						+ HttpChunk.class.getSimpleName() + " without "
						+ HttpMessage.class.getSimpleName());
			}

			// Merge the received chunk into the content of the current message.
			HttpChunk chunk = (HttpChunk) msg;

			if (getContentLength(cmsg) > maxContentLength
					- chunk.getContent().readableBytes()) {
				// TODO: Respond with 413 Request Entity Too Large
				// and discard the traffic or close the connection.
				// No need to notify the upstream handlers - just log.
				// If decoding a response, just throw an exception.
				throw new TooLongFrameException("HTTP content length exceeded "
						+ maxContentLength + " bytes.");
			}

			appendContent(cmsg, chunk.getContent());
			if (chunk.isLast()) {
				this.currentMessage = null;

				// Merge trailing headers into the message.
				if (chunk instanceof HttpChunkTrailer) {
					HttpChunkTrailer trailer = (HttpChunkTrailer) chunk;
					for (Entry<String, String> header : trailer.getHeaders()) {
						cmsg.setHeader(header.getKey(), header.getValue());
					}
				}

				// Set the 'Content-Length' header.
				cmsg.setHeader(HttpHeaders.Names.CONTENT_LENGTH,
						String.valueOf(getContentLength(cmsg)));

				// All done - generate the event.
				endMessage(cmsg);
				return cmsg;
			}
			return null;
		} else {
			// Neither HttpMessage or HttpChunk
			throw new IOException(msg + " neither HttpMessage or HttpChunk");
		}
	}
}
